/**
 * Octopus, an XMMS2 client.
 * Copyright (C) 2014 Octopus Team
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

namespace OctopusUi {
	public enum LayoutEnum {GRID, DEFORMED}

	public class UiBrowsePanel : Mx.Frame, IKeyFocusActor {
		private const int ALBUM_SIZE = 150;

		private Mx.Grid gridLayout;
		private Mx.Frame browseScrollViewFrame; 
		private Mx.BoxLayout horizontalScrollLayout; 
		private OctopusMx.MxGrid deformedLayout;
		private Clutter.Clone reflection;
		private OctopusMx.MxReflectionEffect reflectionEffect;
		private OctopusMx.MxZDeform deformEffect;
		public Clutter.DesaturateEffect desaturateEffect;
		public Mx.Stack browsePanelSearchStack;
		private Gtk.Entry browsePanelSearchGtkEntry;
		private Clutter.Timeline timelineSearchClose;
		private ulong notifyTextSignalId = 0;
		private Octopus.Albumlist albumlist;
		private int loadedCounter = 0;

		public bool use_texture_cache { get; set; default = false; }
		//note: change of layout requires load () to reload data
		public LayoutEnum layout { get; set; default = LayoutEnum.DEFORMED; }

		public signal void load_finished ();
		public signal void show_message_bar_text (string message);
		public signal void tracklist_panel_show (Octopus.IMedialibInfo album_model);

		public UiBrowsePanel () {
			this.notify["allocation"].connect (() => {
				var padding_top = this.deformedLayout.get_padding_top ();
				var padding_bottom = this.deformedLayout.get_padding_bottom ();
				var row_spacing = this.deformedLayout.row_spacing;
				var target_height = this.get_stage ().height * 0.6;
				//var rows = (target_height - padding_top - padding_bottom) /  (row_spacing + ALBUM_SIZE);
				//rows = GLib.Math.round (rows);
				var rows = this.deformedLayout.get_rows_from_target_height ((float)target_height, ALBUM_SIZE);
				this.browseScrollViewFrame.height = (float)(padding_top + padding_bottom + rows * (row_spacing + ALBUM_SIZE));
				this.deformedLayout.height = this.browseScrollViewFrame.height;
				this.reflection.height = this.get_stage ().height - this.browseScrollViewFrame.height - /*bottomPanel*/30;
				this.deformedLayout.visible_width = this.get_stage ().width;
			});

			var browsePanelStack = new Mx.Stack ();
			browsePanelStack.name = "browsePanel-background";
			browsePanelStack.x_align = Clutter.ActorAlign.FILL;
			browsePanelStack.y_align = Clutter.ActorAlign.FILL;
			browsePanelStack.reactive = true;
			this.add_child (browsePanelStack);
			browsePanelStack.button_release_event.connect(() => {
				key_focus ();
				return true; //stop
			});

			//Layout: Grid >>>
			var gridScrollView = new Mx.KineticScrollView ();
			gridScrollView.style_class = "browsePanel-scrollview";
			gridScrollView.snap_on_page = false;
			browsePanelStack.add_child (gridScrollView);

			this.gridLayout = new Mx.Grid ();
			this.gridLayout.style_class = "browsePanel-layout";
			this.gridLayout.orientation = Mx.Orientation.VERTICAL;
			this.gridLayout.homogenous_rows = true;
			this.gridLayout.homogenous_columns = true;
			this.gridLayout.row_spacing = 5;
			this.gridLayout.column_spacing = 5;
			gridScrollView.add_child (this.gridLayout);
			//Layout: Grid <<<

			//Layout: Deformed >>>
			var browseReflectionLayout = new Mx.BoxLayout ();
			browseReflectionLayout.orientation = Mx.Orientation.VERTICAL;
			browsePanelStack.add_child (browseReflectionLayout);

			this.browseScrollViewFrame = new Mx.Frame ();
			browseReflectionLayout.add_child (this.browseScrollViewFrame);
			var browseScrollView = new Mx.KineticScrollView ();
			browseScrollView.style_class = "browsePanel-scrollview";
			browseScrollView.snap_on_page = false;
			this.browseScrollViewFrame.add_child (browseScrollView);

			//horizontal layout to get horizontal scrolling; scrollview.scroll_policy does not have the same effect
			this.horizontalScrollLayout = new Mx.BoxLayout ();
			this.horizontalScrollLayout.orientation = Mx.Orientation.HORIZONTAL;
			browseScrollView.add_child (this.horizontalScrollLayout);
			this.horizontalScrollLayout.horizontal_adjustment.notify["value"].connect (() => {
				this.deformedLayout.adjustment = (float)this.horizontalScrollLayout.horizontal_adjustment.value;
			});

			this.deformedLayout = new OctopusMx.MxGrid ();
			this.deformedLayout.style_class = "browsePanel-layout";
			this.deformedLayout.row_spacing = 5;
			this.deformedLayout.column_spacing = 5;
			this.deformedLayout.visible_width = this.get_stage ().width;
			this.horizontalScrollLayout.add_child (this.deformedLayout);

			this.reflection = new Clutter.Clone (this.browseScrollViewFrame);
			this.reflectionEffect = new OctopusMx.MxReflectionEffect ();
			this.reflectionEffect.opacity = 0.3f;
			this.reflection.add_effect_with_name ("Reflection", this.reflectionEffect);
			browseReflectionLayout.add_child (this.reflection);

			this.deformEffect = new OctopusMx.MxZDeform (this.deformedLayout);
			browseReflectionLayout.add_effect_with_name ("DeformZ", this.deformEffect);

			this.desaturateEffect = new Clutter.DesaturateEffect (0.7);
			this.desaturateEffect.enabled = false;
			browseReflectionLayout.add_effect_with_name ("Desaturate", this.desaturateEffect);
			//Layout: Deformed <<<

			this.notify["layout"].connect (() => {
				GLib.debug ("Layout changed event: %d", layout);
				switch (layout) {
					case LayoutEnum.GRID:
						gridLayout.visible = true;
						browseReflectionLayout.visible = false;
						break;
					case LayoutEnum.DEFORMED:
						gridLayout.visible = false;
						browseReflectionLayout.visible = true;
						break;
				}
			});

			this.browsePanelSearchStack = new Mx.Stack ();
			this.browsePanelSearchStack.x_align = Clutter.ActorAlign.START;
			this.browsePanelSearchStack.y_align = Clutter.ActorAlign.START;
			this.browsePanelSearchStack.visible = false;
			browsePanelStack.add_child (this.browsePanelSearchStack);
			this.browsePanelSearchGtkEntry = new Gtk.Entry ();
			this.browsePanelSearchGtkEntry.name = "browsePanel-search-entry";
			this.browsePanelSearchGtkEntry.width_request = 200;
			this.browsePanelSearchGtkEntry.height_request = 40;
			this.browsePanelSearchGtkEntry.margin = 5;
			var browsePanelSearchEntry = new GtkClutter.Actor ();
			browsePanelSearchEntry.contents = this.browsePanelSearchGtkEntry;
			this.browsePanelSearchStack.add_child (browsePanelSearchEntry);
			var browsePanelSearchCloseButton = new Mx.Button ();
			browsePanelSearchCloseButton.style_class = "browsePanel-search-close";
			browsePanelSearchCloseButton.x_align = Clutter.ActorAlign.END;
			browsePanelSearchCloseButton.y_align = Clutter.ActorAlign.START;
			browsePanelSearchCloseButton.width = 20;
			browsePanelSearchCloseButton.height = 20;
			this.browsePanelSearchStack.add_child (browsePanelSearchCloseButton);
			browsePanelSearchCloseButton.clicked.connect (() => {
				browsePanelSearchEntryClose ();
			});
			this.timelineSearchClose = new Clutter.Timeline (3000);
			this.timelineSearchClose.completed.connect (() => {
				if (this.browsePanelSearchStack.visible) browsePanelSearchEntryClose ();
			});

			var browsePanelOverlay = new Mx.Frame ();
			browsePanelOverlay.name = "browsePanel-overlay";
			browsePanelStack.add_child (browsePanelOverlay);

			//create model
			this.albumlist = new Octopus.Albumlist ();
		}

		public void browsePanelSearchEntryClose () {
			this.browsePanelSearchGtkEntry.disconnect (this.notifyTextSignalId);
			this.browsePanelSearchGtkEntry.text = "";
			this.browsePanelSearchStack.visible = false;
		}

		public void load () {
			this.loadedCounter = 0;
			this.gridLayout.remove_all_children ();
			this.deformedLayout.remove_all_children ();
			ulong loadedSignalId = 0;
			loadedSignalId = 
			this.albumlist.loaded.connect (() => {
				this.albumlist.disconnect (loadedSignalId);
				ulong addSignalId = 0;
				addSignalId =
				this.albumlist.add.connect (on_albumlistmodel_add);
				ulong addFinishedSignalId = 0;
				addFinishedSignalId =
				this.albumlist.add_finished.connect (() => {
					this.albumlist.disconnect (addSignalId);
					this.albumlist.disconnect (addFinishedSignalId);
					Clutter.Actor activeLayout = null;
					switch (layout) {
						case LayoutEnum.GRID:
							activeLayout = this.gridLayout;
							break;
						case LayoutEnum.DEFORMED:
							activeLayout = this.deformedLayout;
							break;
					}
					if (activeLayout.get_n_children () == 0) {
						GLib.debug ("EMPTY ALBUMLIST");
						var noResultText = new Mx.Label ();
						noResultText.style_class = "noResultText-label";
						noResultText.width = 200;
						noResultText.text = "no result";
						activeLayout.add_child (noResultText);
						this.deformEffect.enabled = false;
						load_finished ();
					} else {
						this.deformEffect.enabled = true;
					}
				});
				this.albumlist.request_data ();
			});
			this.albumlist.load ();
		}

		private void on_albumlistmodel_add (Octopus.IMedialibQuery albumlist_model, Octopus.IMedialibInfo album_model) {
			var entry = new UiAlbum ((Octopus.Album) album_model, this.use_texture_cache);
			switch (layout) {
				case LayoutEnum.GRID: this.gridLayout.add_child (entry); break;
				case LayoutEnum.DEFORMED: this.deformedLayout.add_child (entry); break;
			}
			entry.albumArt.album_art_loaded.connect(() => {
				this.loadedCounter++;
				if (this.loadedCounter == this.albumlist.get_size ()) {
					load_finished ();
				}
			});
			entry.albumArt.loadAlbumArt (((Octopus.Album)album_model).picture_front);
			entry.album_enter_event.connect (() => {
				show_message_bar_text (  ((Octopus.Album)album_model).get_artist_performer ()
				                       + " - "
				                       + ((Octopus.Album)album_model).get_album_partofset ());
			});
			entry.album_leave_event.connect (() => {
				show_message_bar_text ("");
			});
			entry.button_release_double_click.connect (() => {
				tracklist_panel_show (album_model);
			});
		}

		public void onKeyPressEvent (Gdk.EventKey event) {
			Clutter.Actor activeLayout = null;
			Mx.Scrollable scrollActor = null;

			switch (layout) {
				case LayoutEnum.GRID:
					activeLayout = this.gridLayout;
					scrollActor  = this.gridLayout;
					break;
				case LayoutEnum.DEFORMED:
					activeLayout = this.deformedLayout;
					scrollActor  = this.horizontalScrollLayout;
					break;
			}

			if (event.keyval == Gdk.Key.Home) { //Home
				scrollActor.horizontal_adjustment.value = 0;
			}
			if (event.keyval == Gdk.Key.End) { //End
				var album_x = activeLayout.get_child_at_index (activeLayout.get_n_children ()-1).x;
				scrollActor.horizontal_adjustment.value = album_x - (this.get_stage ().width/2);
			}
			if (event.keyval == Gdk.Key.Page_Up) { //Page_Up
				scrollActor.horizontal_adjustment.value =
				    scrollActor.horizontal_adjustment.value
				  - (this.get_stage ().width - ALBUM_SIZE);
			}
			if (event.keyval == Gdk.Key.Page_Down) { //Page_Down
				scrollActor.horizontal_adjustment.value =
				    scrollActor.horizontal_adjustment.value
				  + (this.get_stage ().width - ALBUM_SIZE);
			}
			if (   (event.keyval == Gdk.Key.Up)
				|| (event.keyval == Gdk.Key.Left)) { //Up, Left
				scrollActor.horizontal_adjustment.value =
				    scrollActor.horizontal_adjustment.value
				  - ALBUM_SIZE;
			}
			if (   (event.keyval == Gdk.Key.Down)
				|| (event.keyval == Gdk.Key.Right)) { //Down, Right
				scrollActor.horizontal_adjustment.value =
				    scrollActor.horizontal_adjustment.value
				  + ALBUM_SIZE;
			}

			if (   (event.str.get_char (0).isprint ())
			    && (this.browsePanelSearchStack.visible == false)) {
				this.notifyTextSignalId =
				this.browsePanelSearchGtkEntry.notify["text"].connect (() => {
					//search_string_position returns -1 for no match
					var searchTextScrollPosition = this.albumlist.search_string_position (this.browsePanelSearchGtkEntry.text);
					if (searchTextScrollPosition >= 0) {
						var album_x = activeLayout.get_child_at_index (searchTextScrollPosition).x;
						scrollActor.horizontal_adjustment.value = album_x - 100;
					}
					this.timelineSearchClose.stop ();
					this.timelineSearchClose.start ();
				});
				this.browsePanelSearchStack.visible = true;
				this.browsePanelSearchGtkEntry.text = event.str;
				this.browsePanelSearchGtkEntry.grab_focus ();
				this.timelineSearchClose.start ();
			}
		}
	}

	public class UiAlbum : Mx.Frame, OctopusMx.IMxGridActor {
		private const int ALBUM_SIZE = 150;

		public float stored_z_position { get; set; }
		public float stored_rotation_angle_y { get; set; }

		public UiAlbumArt albumArt; 

		public signal void album_enter_event ();
		public signal void album_leave_event ();
		public signal void button_release_double_click ();

		public UiAlbum (Octopus.Album album_model, bool use_texture_cache) {
			var albumStack = new Mx.Stack ();
			albumStack.name = "album-background";
			this.add_child (albumStack);
			this.albumArt = new UiAlbumArt (ALBUM_SIZE, use_texture_cache);
			this.albumArt.defaultAlbumArtArtist.text = album_model.get_artist_performer ();
			this.albumArt.defaultAlbumArtAlbum.text  = album_model.album;
			albumStack.add_child (this.albumArt);

			var albumOverlay = new Mx.Frame ();
			albumOverlay.name = "album-overlay";
			albumOverlay.reactive = true;
			albumStack.add_child (albumOverlay);
			albumOverlay.enter_event.connect (() => {
				album_enter_event ();
				return false; //propagate
			});
			albumOverlay.leave_event.connect (() => {
				album_leave_event ();
				return false; //propagate
			});
			albumOverlay.button_release_event.connect((/*Clutter.ButtonEvent*/ event) => {
				if ((event.button == 1) && (event.click_count == 1)) {  //left mouse button
				}
				if ((event.button == 1) && (event.click_count == 2)) { //double click (left)
					button_release_double_click ();
					return true; //stop
				}
				if ((event.button == 3) && (event.click_count == 1)) {  //right mouse button
				}
				return false; //propagate
			});
		}
	}

}
